package fr.tvbarthel.cheerleader.library.media;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.media.MediaMetadataRetriever;
import android.media.RemoteControlClient;
import android.os.Build;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Log;
import fr.tvbarthel.cheerleader.library.client.SoundCloudTrack;
import fr.tvbarthel.cheerleader.library.remote.RemoteControlClientCompat;
import fr.tvbarthel.cheerleader.library.remote.RemoteControlHelper;
/**
* Wrapper used to encapsulate {@link android.support.v4.media.session.MediaSessionCompat} behaviour
* as well as a remote control client for lock screen on pre Lollipop.
*/
public class MediaSessionWrapper {
/**
* Stopped state.
* See also :
* {@link android.support.v4.media.session.PlaybackStateCompat#STATE_STOPPED}
* {@link android.media.RemoteControlClient#PLAYSTATE_STOPPED}
*/
public static final int PLAYBACK_STATE_STOPPED = 0x00000000;
/**
* Playing state.
* See also :
* {@link android.support.v4.media.session.PlaybackStateCompat#STATE_PLAYING}
* {@link android.media.RemoteControlClient#PLAYSTATE_PLAYING}
*/
public static final int PLAYBACK_STATE_PLAYING = 0x00000001;
/**
* Paused state.
* See also :
* {@link android.support.v4.media.session.PlaybackStateCompat#STATE_PAUSED}
* {@link android.media.RemoteControlClient#PLAYSTATE_PAUSED}
*/
public static final int PLAYBACK_STATE_PAUSED = 0x00000002;
/**
* Action used to catch broadcast from {@link MediaSessionReceiver}
*/
static final String ACTION_TOGGLE_PLAYBACK = "fr.tvbarthel.simplesoundcloud.library.media.TOGGLE_PLAYBACK";
/**
* Action used to catch broadcast from {@link .MediaSessionReceiver}
*/
static final String ACTION_NEXT_TRACK = "fr.tvbarthel.simplesoundcloud.library.media.NEXT_TRACK";
/**
* Action used to catch broadcast from {@link MediaSessionReceiver}
*/
static final String ACTION_PREVIOUS_TRACK = "fr.tvbarthel.simplesoundcloud.library.media.PREVIOUS_TRACK";
/**
* Tag.
*/
private static final String TAG = MediaSessionWrapper.class.getSimpleName();
/**
* Media session used to interact with media controllers, volume key and media buttons.
*/
private MediaSessionCompat mMediaSession;
/**
* Component name used to register receiver which catch lock screen remote control client
* on pre Lollipop.
*/
private ComponentName mMediaButtonReceiverComponent;
/**
* Remote control client used on pre Lollipop.
*/
private RemoteControlClientCompat mRemoteControlClientCompat;
/**
* Current callback object.
*/
private MediaSessionWrapperCallback mCallback;
/**
* Audio manager used to catch audio focus event.
*/
private AudioManager mAudioManager;
/**
* App package name at runtime used for the Component name.
*/
private String mRuntimePackageName;
/**
* Receiver used to catch lock screen event on pre Lollipop devices.
*/
private LockScreenReceiver mLockScreenReceiver;
/**
* Used to register/unregister lock screen receiver.
*/
private LocalBroadcastManager mLocalBroadcastManager;
/**
* Builder used to set the playback state to the Media session compat.
*/
private PlaybackStateCompat.Builder mStateBuilder;
private Context mContext;
/**
* Wrapper used to encapsulate {@link android.support.v4.media.session.MediaSessionCompat} behaviour
* as well as a remote control client for lock screen on pre Lollipop.
*
* @param context holding context.
* @param callback callback used to catch media session or lock screen events.
* @param audioManager audio manager used to request the focus.
*/
public MediaSessionWrapper(Context context, MediaSessionWrapperCallback callback, AudioManager audioManager) {
mContext = context;
mCallback = callback;
mAudioManager = audioManager;
mRuntimePackageName = context.getPackageName();
initLockScreenRemoteControlClient(context);
initPlaybackStateBuilder();
mMediaSession = new MediaSessionCompat(context, TAG);
mMediaSession.setCallback(new MediaSessionCallback());
mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
| MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
}
/**
* Should be called to released the internal component.
*/
@SuppressWarnings("deprecation")
public void onDestroy() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
mRemoteControlClientCompat.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
mAudioManager.unregisterMediaButtonEventReceiver(mMediaButtonReceiverComponent);
mLocalBroadcastManager.unregisterReceiver(mLockScreenReceiver);
}
mMediaSession.release();
}
/**
* Propagate the playback state to the media session and the lock screen remote control.
* <p/>
* See also :
* {@link fr.tvbarthel.cheerleader.library.media.MediaSessionWrapper#PLAYBACK_STATE_STOPPED}
* {@link fr.tvbarthel.cheerleader.library.media.MediaSessionWrapper#PLAYBACK_STATE_PLAYING}
* {@link fr.tvbarthel.cheerleader.library.media.MediaSessionWrapper#PLAYBACK_STATE_PAUSED}
*
* @param state playback state.
*/
@SuppressWarnings("deprecation")
public void setPlaybackState(int state) {
switch (state) {
case PLAYBACK_STATE_STOPPED:
setRemoteControlClientPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
setMediaSessionCompatPlaybackState(PlaybackStateCompat.STATE_STOPPED);
mMediaSession.setActive(false);
break;
case PLAYBACK_STATE_PLAYING:
setRemoteControlClientPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
setMediaSessionCompatPlaybackState(PlaybackStateCompat.STATE_PLAYING);
mMediaSession.setActive(true);
break;
case PLAYBACK_STATE_PAUSED:
setRemoteControlClientPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED);
setMediaSessionCompatPlaybackState(PlaybackStateCompat.STATE_PAUSED);
break;
default:
Log.e(TAG, "Unknown playback state.");
break;
}
}
/**
* Update meta data used by the remote control client and the media session.
*
* @param track track currently played.
*/
@SuppressWarnings("deprecation")
public void setMetaData(SoundCloudTrack track) {
setMetaData(track, null);
}
/**
* Update meta data used by the remote control client and the media session.
*
* @param track track currently played.
* @param artwork track artwork.
*/
@SuppressWarnings("deprecation")
public void setMetaData(SoundCloudTrack track, Bitmap artwork) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// set meta data on the lock screen for pre lollipop.
mRemoteControlClientCompat.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
RemoteControlClientCompat.MetadataEditorCompat mediaEditorCompat
= mRemoteControlClientCompat.editMetadata(true)
.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, track.getTitle())
.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, track.getArtist());
if (artwork != null) {
mediaEditorCompat.putBitmap(
RemoteControlClientCompat.MetadataEditorCompat.METADATA_KEY_ARTWORK, artwork);
}
mediaEditorCompat.apply();
}
// set meta data to the media session.
MediaMetadataCompat.Builder metadataCompatBuilder = new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, track.getTitle())
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, track.getArtist());
if (artwork != null) {
metadataCompatBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, artwork);
}
mMediaSession.setMetadata(metadataCompatBuilder.build());
setMediaSessionCompatPlaybackState(PlaybackStateCompat.STATE_PLAYING);
}
/**
* Propagate playback state to the remote control client on the lock screen.
*
* @param state playback state.
*/
private void setRemoteControlClientPlaybackState(int state) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
mRemoteControlClientCompat.setPlaybackState(state);
}
}
/**
* Propagate playback state to the media session compat.
*
* @param state playback state.
*/
private void setMediaSessionCompatPlaybackState(int state) {
mStateBuilder.setState(state, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f);
mMediaSession.setPlaybackState(mStateBuilder.build());
}
/**
* Initialize the playback state builder with supported actions.
*/
private void initPlaybackStateBuilder() {
mStateBuilder = new PlaybackStateCompat.Builder();
mStateBuilder.setActions(
PlaybackStateCompat.ACTION_PLAY
| PlaybackStateCompat.ACTION_PAUSE
| PlaybackStateCompat.ACTION_PLAY_PAUSE
| PlaybackStateCompat.ACTION_SKIP_TO_NEXT
| PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
);
}
/**
* Initialize the remote control client on the lock screen.
*
* @param context holding context.
*/
@SuppressWarnings("deprecation")
private void initLockScreenRemoteControlClient(Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
mMediaButtonReceiverComponent = new ComponentName(
mRuntimePackageName, MediaSessionReceiver.class.getName());
mAudioManager.registerMediaButtonEventReceiver(mMediaButtonReceiverComponent);
if (mRemoteControlClientCompat == null) {
Intent remoteControlIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
remoteControlIntent.setComponent(mMediaButtonReceiverComponent);
mRemoteControlClientCompat = new RemoteControlClientCompat(
PendingIntent.getBroadcast(context, 0, remoteControlIntent, 0));
RemoteControlHelper.registerRemoteControlClient(mAudioManager, mRemoteControlClientCompat);
}
mRemoteControlClientCompat.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
mRemoteControlClientCompat.setTransportControlFlags(RemoteControlClient.FLAG_KEY_MEDIA_PLAY
| RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
| RemoteControlClient.FLAG_KEY_MEDIA_NEXT
| RemoteControlClient.FLAG_KEY_MEDIA_STOP
| RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
| RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE);
registerLockScreenReceiver(context);
}
}
/**
* Register the lock screen receiver used to catch lock screen media buttons events.
*
* @param context holding context.
*/
private void registerLockScreenReceiver(Context context) {
mLockScreenReceiver = new LockScreenReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_TOGGLE_PLAYBACK);
intentFilter.addAction(ACTION_NEXT_TRACK);
intentFilter.addAction(ACTION_PREVIOUS_TRACK);
mLocalBroadcastManager = LocalBroadcastManager.getInstance(context);
mLocalBroadcastManager.registerReceiver(mLockScreenReceiver, intentFilter);
}
/**
* Catch callback from media session.
*/
private final class MediaSessionCallback extends MediaSessionCompat.Callback {
@Override
public void onPlay() {
super.onPlay();
mCallback.onPlay();
}
@Override
public void onPause() {
super.onPause();
mCallback.onPause();
}
@Override
public void onSkipToNext() {
super.onSkipToNext();
mCallback.onSkipToNext();
}
@Override
public void onSkipToPrevious() {
super.onSkipToPrevious();
mCallback.onSkipToPrevious();
}
}
/**
* Catch callback from the {@link fr.tvbarthel.cheerleader.library.media.MediaSessionReceiver}
* which catch broadcast from the remote control client on the lock screen of pre Lollipop devices.
*/
private final class LockScreenReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null) {
switch (intent.getAction()) {
case ACTION_TOGGLE_PLAYBACK:
mCallback.onPlayPauseToggle();
break;
case ACTION_NEXT_TRACK:
mCallback.onSkipToNext();
break;
case ACTION_PREVIOUS_TRACK:
mCallback.onSkipToPrevious();
break;
default:
break;
}
}
}
}
/**
* Media session wrapper callbacks.
*/
public interface MediaSessionWrapperCallback {
/**
* Called when play is requested.
*/
void onPlay();
/**
* Called when pause is requested.
*/
void onPause();
/**
* Called when next track should be played.
*/
void onSkipToNext();
/**
* Called when previous track should be played.
*/
void onSkipToPrevious();
/**
* Called when play/pause button is toggled.
*/
void onPlayPauseToggle();
}
}